<?php
/**
* DIXon Generation 1 - JavaScript Client Pages
* 
* PHP Bindings for PHP 5.2.x and above
* 
* Copyright (c) 2008, Artem Ploujnikov
* All rights reserved.
* 
* Redistribution and use in source and binary forms, with or without modification, 
* are permitted provided  that the following conditions are met:
* 
*  * Redistributions of source code must retain the above copyright notice, this 
*    list of conditions and the following disclaimer.
*  * Redistributions in binary form must reproduce the above copyright notice, this 
* 	 list of conditions and the following disclaimer in the documentation and/or 
*    other materials provided with the distribution.
* *  Neither the name of Artem Ploujnikov nor the names of the project's contributors 
* 	 may be used to endorse or promote products derived from this software without 
*    specific prior written permission.
* 
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
* IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
* POSSIBILITY OF SUCH DAMAGE. 
*/


/**
* A lightweight script creation helper, which makes it 
* possible to programmatically output JavaScript/EcmaScript Code
*/
class DiXon_ScriptWriter {
	const ArrayStart = '[';
	const ArrayEnd = ']';
	const ObjectStart = '{';
	const ObjectEnd = '}';
	const ItemEnd = ',';
	const NameValueSeparator = ':';
	
	/**
	* The output stream to which the script will be written
	* 
	* @var IOutputStream
	*/
	private $output;
	
	/**
	* Creates a new script writer instance
	* 
	* @param IOutputStream $output the output scream
	* 
	* @return DiXon_ScriptWriter
	*/
	public function __construct(IOutputStream $output) {
		$this->output = $output;
	}
	
	/**
	* Starts a JavaScript array by outputting a '[' 
	* character
	* 
	* @return DiXon_ScriptWriter
	*/
	public function startArray() {
		$this->output->write(self::ArrayStart);
		return $this;
	}

	/**
	* Closes a JavaScript array by outputting a '[] 
	* character
	* 
	* @return DiXon_ScriptWriter
	*/
	public function endArray() {
		$this->output->write(self::ArrayEnd);
		return $this;
	}

	
	/**
	* Starts an anonymous object
	* 
	* @return DiXon_ScriptWriter 
	*/
	public function startObject() {
		$this->output->write(self::ObjectStart);
		return $this;
	}
	
	/**
	* Closes an anonymous object
	* 
	* @return DiXon_ScriptWriter 
	*/
	public function endObject() {
		$this->output->write(self::ObjectEnd);
		return $this;
	}
	
	/**
	* Outputs a separator (','), which separates
	* array elements and object properties.
	* 
	* @return DiXon_ScriptWriter
	*/
	public function endItem() {
		$this->output->write(self::ItemEnd);
		return $this;
	}
	
	/**
	* Outputs the value to the underlying stream
	* as a JavaScript string
	* 
	* @param string $text any text content
	* 
	* @return  DiXon_ScriptWriter
	*/
	public function writeText($text) {
		$this->output->write(self::escape($text));
		return $this;
	}
	
	/**
	* Outputs the name of a property in a JavaScript
	* object. It's up to the caller to follow up with a
	* value, which can be a string, an array, an object
	* or any other JavaScript type
	* 
	* @param string $key the name of the key
	* 
	* @return DiXon_ScriptWriter
	*/
	public function writeKey($key) {
		$this->output->write(self::escape($key) . self::NameValueSeparator);
		return $this;
	}
	
	/**
	* Outputs a key/value pair within the context of
	* a JavaScript object  The ending comma is NOT output by this 
	* method
	* 
	* @param string $key 	the name of the property
	* @param string $value  the property's value
	* 
	* @return DiXon_ScriptWriter
	*/
	public function writeKeyValuePair($key, $value) {
		$this->output->write(
			self::escape($key) 
			. self::NameValueSeparator 
			. self::escape($value)
		);
		return $this;
	}
	
	/**
    * Outputs the provided content directly to the underlying 
    * stream without processing or escaping. The content should 
    * represent valid JavaScript code that is applicable
	* to the current context.
	* 
	* @param mixed $content raw JavaScript code
	* 
	* @return DiXon_ScriptWriter
	*/                                 
	public function writeRaw($content) {
		$this->output->write($content);
		return $this;
	}
	
	/**
	* Starts a method or function call
	* 
	* @param string $method the name of the method to call
	* 
	* @return DiXon_ScriptWriter
	*/
	public function startCall($method) {
		$this->output->write($method . '(');
		return $this;
	}
	
	/**
	* Ends a method call
	* 
	* @return DiXon_ScriptWriter
	*/
	public function endCall() {
		$this->output->write('); ');
		return $this;
	}
	
	/**
	* Writes out a complete error 
	* 
	* @param string $method 	the name of the method to call
	* @param array $arguments	the arguments to pass to the method
	* 
	* @return DiXon_ScriptWriter
	*/
	public function callMethod($method, $arguments) {
		$this->startCall($method);
		
		if (!empty($arguments)) {
			$this->writeObject($arguments[0]);
			for($i=1,$length=count($arguments);$i<$length;$i++) {
				$this->endItem();
				$this->writeObject($arguments[1]);
			}
		}
		
		return $this->endCall();
	}
	
	private function writeObject($object) {
		if (is_string($object)) {
			$this->writeText($object);
		} else {
			$this->writeRaw($object);
		}
	}
	
	
	/**
	* Outputs a scriptable item to the underlying
	* stream
	* 
	* @param IScriptable $item the item to output
	* @return DiXon_ScriptWriter
	*/
	public function writeItem(IScriptable $item) {
		$item->writeScript($this);
		return $this;
	}
		
	
	/**
	* Outputs an array of scriptable items to the
	* underlying stream. Each element of the array
	* should implement IScriptable
	* 
	* @param array $items an array of scriptable objects
	* 
	* @return DiXon_ScriptWriter
	*/
	public function writeItems(array $items) {
		$this->startArray();    	
    	
    	$length = count($items);
    	if ($length > 0) {
			$this->writeItem($items[0]);
			for ($i=1;$i<$length;$i++) {
				$this->endItem();
				$this->writeItem($items[$i]);
			}
		}
		
		return $this->endArray();		
	}
	
	/**
	* Converts a PHP string to an escaped and quoted 
	* JavaScript string
	* 
	* @param string $value an arbitrary PHP string
	* 
	* @return string
	*/
	public function escape($value) {
		return
			($value === null) ?
				'null' : 
				"'" . 
				str_replace(
					array('\\', "'", "\n"),
					array('\\\\', "\\'", "' + '\\n' + '"),
					$value
				).
				"'";
	}

}

?>